//
// Created by LiuYou on 2021/10/23.
//

#include <unistd.h>
#include <algorithm>
#include <csignal>
#include "TBF.hpp"


class TokenBucketFilter {
public:
    explicit TokenBucketFilter(int cps = 0, int burst = 0/*, int tokens = 0*/) :
            cps( cps ), burst( burst ), tokens( 0 ), pos( 0 ) {}

    ~TokenBucketFilter() = default;

public:
    int cps;
    int burst;
    int tokens;
    int pos;

};


static TokenBucketFilter* tbfPolicyArray[TBF_MAX];

static bool isInited{false};

static sighandler_t alarmHandlerSave;


static int getFreePos() {
    for (int i = 0; i < TBF_MAX; ++i) {
        if (tbfPolicyArray[i] == nullptr) {
            return i;
        }
    }
    return -1;
}

// 1.
//Tbf* makeTbf(int cps, int burst) {
////    TokenBucketFilter* me = new TokenBucketFilter( cps, burst );
////    if (me == nullptr) {
////        return nullptr;
////    }
//////    tbfPolicyArray[getFreePos()] = me;
////
////    int pos = getFreePos();
////    if (pos < 0) {
////        delete me;
////        return nullptr;
////    }
////    tbfPolicyArray[pos] = me;
//
//
//    // 信号来了为所有的令牌桶加Token, 即攒权限。
//    signal( SIGALRM, );
//    // todo 笔记: alarm();不能在这儿写！ 因为: 如果用户需要使用两种不同速率的令牌桶。
//    //  用户就会调用两次 本函数 makeTbf(); , 而 alarm(); 只能出现一次, 即如果程序中同时有多个 alarm(); , 那么
//    //  定时就会出错。所以 alarm();函数只能执行一次。那如何让 alarm(); 只执行一次呢? 见没有注释的本函数。
//    alarm( 1 );
//
//
//    int pos = getFreePos();
//    if (pos < 0) {
//        return nullptr;
//    }
//    TokenBucketFilter* me = new TokenBucketFilter( cps, burst );
//    if (me == nullptr) {
//        return nullptr;
//    }
//    me->pos = pos;
//    tbfPolicyArray[pos] = me;
//
//
//    return me;
//}

// 2.
//Tbf* makeTbf(int cps, int burst) {
//
//    if (!isInited) {
//        // 信号来了为所有的令牌桶加Token, 即攒权限。
//        signal( SIGALRM, );
//        // todo 笔记: alarm();不能在这儿写！ 因为: 如果用户需要使用两种不同速率的令牌桶。
//        //  用户就会调用两次 本函数 makeTbf(); , 而 alarm(); 只能出现一次, 即如果程序中同时有多个 alarm(); , 那么
//        //  定时就会出错。所以 alarm();函数只能执行一次。那如何让 alarm(); 只执行一次呢?
//        alarm( 1 );
//        isInited = true;
//    }
//
//
//    int pos = getFreePos();
//    if (pos < 0) {
//        return nullptr;
//    }
//    TokenBucketFilter* me = new TokenBucketFilter( cps, burst );
//    if (me == nullptr) {
//        return nullptr;
//    }
//    me->pos = pos;
//    tbfPolicyArray[pos] = me;
//
//
//    return me;
//}



static void alarmHandler(int signal) {
    for (int i = 0; i < TBF_MAX; ++i) {
        if (tbfPolicyArray[i] != nullptr) {
            tbfPolicyArray[i]->tokens += tbfPolicyArray[i]->cps;
            if (tbfPolicyArray[i]->tokens > tbfPolicyArray[i]->burst) {
                tbfPolicyArray[i]->tokens = tbfPolicyArray[i]->burst;
            }
        }
    }
    alarm( 1 );
}


/**
 * @brief 如果当前进程在某个位置异常结束了, 那应该把当前使用中的令牌桶的内存释放掉。
 *
 * <p>永远不要觉得你在写一个完整的函数, 也不要永远觉得你在单枪匹马的工作, 你要认识到
 * 你的代码内容放在一个大环境下就是一个小模块, 你要保证进入你这个模块来全局的状态和
 * 出去你这个模块来全局的状态是一样的！
 *
 * <p>那么在当前的这个模块上, 这个库其实改变了 信号 alarm 的功能, 那你不知道出了你
 * 这个模块, 别人写的代码使用 信号 alarm 还有其它的功能, 或者不需要时钟函数 alarm,
 * 而出来了你当前的这个模块, 时钟还在一秒钟发送一个。或者别人在你这个模块之外也对 SIGALRM 定义了行为(即信号处理函数)。
 * 所以, 要知道当前这个模块把什么改变了, 然后再这个模块结束时, 把进入这个模块的状态进行恢复。
 *
 * <p>当在模块卸载函数中将所有的内容释放掉后, 那么问题就是在哪里调用这个模块卸载函数?
 * <p>一定不能在 destroyTbf(); 函数中调用。这里我是不知道为什么的?
 * <p>将这个模块卸载函数在模块加载函数中使用 atexit(); 来注册。
 */
static void moduleUnload() {
    // 第一: 把 SIGALRM 的默认动作恢复。
    signal( SIGALRM, alarmHandlerSave );
    // 第二: 把 alarm信号 关掉, 不再发出。
    alarm( 0 );
    // 第三: 然后是异常终止的话, 应该把没来得及释放的内容释放掉。
    for (int i = 0; i < TBF_MAX; ++i) {
//        if (tbfPolicyArray[i] != nullptr) {
//            delete tbfPolicyArray[i];
//        }
        free( tbfPolicyArray[i] );
    }
}


static void moduleLoad() {
    // 信号来了为所有的令牌桶加Token, 即攒权限。
    alarmHandlerSave = signal( SIGALRM, alarmHandler );
    // todo 笔记: alarm();不能在这儿写！ 因为: 如果用户需要使用两种不同速率的令牌桶。
    //  用户就会调用两次 本函数 makeTbf(); , 而 alarm(); 只能出现一次, 即如果程序中同时有多个 alarm(); , 那么
    //  定时就会出错。所以 alarm();函数只能执行一次。那如何让 alarm(); 只执行一次呢?
    alarm( 1 );

    atexit( moduleUnload );
}


// 3.
Tbf* makeTbf(int cps, int burst) {

    /*
     * todo 笔记: 巧用全局静态变量, 使代码只执行一次。在多次调用一个函数, 函数中的部分代码只执行一次！
     */
    if (!isInited) {
        moduleLoad();
        isInited = true;
    }


    int pos = getFreePos();
    if (pos < 0) {
        return nullptr;
    }
//    TokenBucketFilter* me = new TokenBucketFilter( cps, burst );
    TokenBucketFilter* me = static_cast<TokenBucketFilter*>(malloc( sizeof( TokenBucketFilter )));
    if (me == nullptr) {
        return nullptr;
    }
    me->pos = pos;
    tbfPolicyArray[pos] = me;


    return me;
}


int destroyTbf(Tbf* tbf) {
    TokenBucketFilter* me = static_cast<TokenBucketFilter*>(tbf);
    tbfPolicyArray[me->pos] = nullptr;
//    delete me;
    free( tbf );

    return 0;
}


extern int fetchToken(Tbf* tbf, int tokens) {
    if (tokens <= 0) {
        return -1;
    }
    TokenBucketFilter* me = static_cast<TokenBucketFilter* >(tbf);
//    if (me->tokens <= 0) {
//        pause();
//    }
    while (me->tokens < 0) {
        pause();
    }

    int retTokens = std::min( me->tokens, tokens );
    me->tokens -= retTokens;


    return retTokens;
}

extern int returnToken(Tbf* tbf, int tokens) {
    if (tokens <= 0) {
        return -1;
    }
    TokenBucketFilter* me = static_cast<TokenBucketFilter*>(tbf);
    me->tokens += tokens;
    if (me->tokens > me->burst) {
        me->tokens = me->burst;
    }

    return tokens;
}